<template>
  <el-card>
    <template #header>
      <div style="display: flex; align-items: center; gap: 12px">
        <span>广州地铁三维可视化</span>
        <el-switch
          v-model="basemapEnabled"
          @change="toggleBasemap"
          active-text="底图"
        />
      </div>
    </template>

    <div
      ref="threeBox"
      style="height: 700px; width: 100%; background: #232323"
    ></div>

    <div style="color: #999; font-size: 13px; margin-top: 8px">
      🚇 多线路/站点/列车 + 城市底图（OSM 瓦片拼接）已集成。
    </div>
  </el-card>

  <el-dialog v-model="showInfo" title="列车信息" width="350px" append-to-body>
    <div v-if="selectedTrain">
      <div>线路：{{ selectedTrain.mesh.userData.lineName }}</div>
      <div>车辆编号：{{ selectedTrain.mesh.userData.trainId }}</div>
      <div>当前位置：{{ getNearestStation(selectedTrain) }}</div>
      <div>当前参数 t: {{ selectedTrain.t.toFixed(2) }}</div>
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import metroData from "@/assets/guangzhou-metro.json";
// import TileLayer from "./utils/TileLayer";

class TileLayer {
  /**
   * @param {THREE.Scene} scene
   * @param {THREE.WebGLRenderer} renderer
   * @param {THREE.PerspectiveCamera} camera
   * @param {Object} opts
   */
  constructor(scene, renderer, camera, opts = {}) {
    this.scene = scene;
    this.renderer = renderer;
    this.camera = camera;

    // 配置
    this.maxZoom = opts.maxZoom ?? 18; // 近景最高层级
    this.minZoom = opts.minZoom ?? 12; // 远景最低层级
    this.baseUrl =
      opts.baseUrl ??
      ((z, x, y) => `https://tile.openstreetmap.org/${z}/${x}/${y}.png`);
    this.tilePx = opts.tilePx ?? 512; // @2x 瓦片为 512
    this.aniso = Math.min(16, renderer.capabilities.getMaxAnisotropy?.() || 1); //各向异性过滤，缩小时更清晰
    this.maxConcurrent = opts.maxConcurrent ?? 8; // 同时下载上限
    this.maxTiles = opts.maxTiles ?? 500; // LRU 上限，避免内存膨胀
    this.yOnGround = opts.yOnGround ?? -0.01; // 底图略低于地面，避开 z-fighting
    // 状态
    this.meshMap = new Map(); // key = `${z}/${x}/${y}` -> {mesh, tex, stamp}
    this.loading = new Map(); // key -> Promise
    this.queue = []; // 等待下载的 key
    this.frameCount = 0; // 节流使用
    this.enabled = true; // 开关
    this.tmpVec = new THREE.Vector3();

    // 用于射线与地面相交
    this.plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); // y=0 平面
    this.ray = new THREE.Ray();
    this.raycaster = new THREE.Raycaster();

    // 预设一个材质与几何缓存（每瓦片独立材质以挂贴图）
    this.geomCache = new Map(); // widthWorld+'x'+heightWorld -> geometry
  }

  setEnabled(v) {
    this.enabled = v;
    if (!v) this.clearAll();
  }

  clearAll() {
    for (const { mesh, tex } of this.meshMap.values()) {
      this.scene.remove(mesh);
      mesh.geometry?.dispose?.();
      mesh.material?.map?.dispose?.();
      mesh.material?.dispose?.();
      tex?.dispose?.();
    }
    this.meshMap.clear();
    this.loading.clear();
    this.queue.length = 0;
  }

  // --- 视野与 LOD ---
  update() {
    if (!this.enabled) return;
    // 节流：每 2 帧更新一次即可
    if (this.frameCount++ % 2 !== 0) return;

    const groundPoly = this._getGroundFootprint();
    if (!groundPoly) return;

    // 以地面多边形的包围盒估计“地面米尺寸”
    let minX = Infinity,
      maxX = -Infinity,
      minZ = Infinity,
      maxZ = -Infinity;
    groundPoly.forEach((p) => {
      minX = Math.min(minX, p.x);
      maxX = Math.max(maxX, p.x);
      minZ = Math.min(minZ, p.z);
      maxZ = Math.max(maxZ, p.z);
    });
    const widthWorld = Math.max(1e-3, maxX - minX);
    const heightWorld = Math.max(1e-3, maxZ - minZ);
    const widthMeters = widthWorld / METER_TO_WORLD;
    const heightMeters = heightWorld / METER_TO_WORLD;

    // 估算一个目标 LOD（zoom）：让瓦片像素尺寸与屏幕密度匹配
    const targetZoom = this._estimateZoom(widthMeters, heightMeters);
    const z = THREE.MathUtils.clamp(targetZoom, this.minZoom, this.maxZoom);

    // 由地面多边形求经纬度包围盒
    // 注意：groundPoly 是世界坐标 -> 反推米坐标 -> 经纬度 -> 瓦片范围
    let minLon = Infinity,
      minLat = Infinity,
      maxLon = -Infinity,
      maxLat = -Infinity;
    groundPoly.forEach((p) => {
      const mx = p.x / METER_TO_WORLD + CENTER_M.x;
      const my = p.z / METER_TO_WORLD + CENTER_M.y;
      // 反投影米->经纬度（简式，反解 y->lat）
      const lon = (mx / ORIGIN_SHIFT) * 180.0;
      const lat =
        (180 / Math.PI) * (2 * Math.atan(Math.exp(my / R)) - Math.PI / 2);
      minLon = Math.min(minLon, lon);
      maxLon = Math.max(maxLon, lon);
      minLat = Math.min(minLat, lat);
      maxLat = Math.max(maxLat, lat);
    });

    // 取瓦片范围并添加一点扩展边距（避免边缘露白）
    const pad = 1;
    const xMin = lon2tile(minLon, z) - pad;
    const xMax = lon2tile(maxLon, z) + pad;
    const yTop = lat2tile(maxLat, z) - pad;
    const yBot = lat2tile(minLat, z) + pad;

    // 目标集合
    const neededKeys = new Set();

    for (let x = xMin; x <= xMax; x++) {
      for (let y = yTop; y <= yBot; y++) {
        const key = `${z}/${x}/${y}`;
        neededKeys.add(key);
        if (!this.meshMap.has(key) && !this.loading.has(key)) {
          this.queue.push(key);
        }
      }
    }

    // 推进下载队列
    this._drainQueue();

    // 卸载不需要的瓦片（LRU）
    for (const [key, rec] of this.meshMap) {
      if (!neededKeys.has(key)) {
        // 延迟清理：可以打一个时间戳，过几秒再删，这里简单直接删
        this.scene.remove(rec.mesh);
        rec.mesh.geometry?.dispose?.();
        rec.mesh.material?.map?.dispose?.();
        rec.mesh.material?.dispose?.();
        rec.tex?.dispose?.();
        this.meshMap.delete(key);
      } else {
        // 更新 LRU 时间戳
        rec.stamp = performance.now();
      }
    }

    // 如果超出上限，LRU 淘汰最旧
    if (this.meshMap.size > this.maxTiles) {
      const arr = [...this.meshMap.entries()];
      arr.sort((a, b) => a[1].stamp - b[1].stamp);
      const overflow = this.meshMap.size - this.maxTiles;
      for (let i = 0; i < overflow; i++) {
        const [key, rec] = arr[i];
        this.scene.remove(rec.mesh);
        rec.mesh.geometry?.dispose?.();
        rec.mesh.material?.map?.dispose?.();
        rec.mesh.material?.dispose?.();
        rec.tex?.dispose?.();
        this.meshMap.delete(key);
      }
    }
  }

  _drainQueue() {
    // 限制并发
    const inflight = this.loading.size;
    const allow = Math.max(0, this.maxConcurrent - inflight);
    for (let i = 0; i < allow && this.queue.length > 0; i++) {
      const key = this.queue.shift();
      this._spawnTile(key);
    }
  }

  async _spawnTile(key) {
    // key: z/x/y
    const [zStr, xStr, yStr] = key.split("/");
    const z = +zStr,
      x = +xStr,
      y = +yStr;
    const url = this.baseUrl(z, x, y);

    const tex = new THREE.Texture();
    tex.anisotropy = this.aniso;
    tex.wrapS = tex.wrapT = THREE.ClampToEdgeWrapping;
    tex.minFilter = THREE.LinearMipMapLinearFilter;
    tex.magFilter = THREE.LinearFilter;

    const p = this._loadImage(url)
      .then((img) => {
        if (!img) throw new Error("img load fail");
        tex.image = img;
        tex.needsUpdate = true;

        // 计算瓦片在世界中的尺寸与中心位置
        // 左上/右下经纬度：
        const lonLeft = tile2lon(x, z);
        const lonRight = tile2lon(x + 1, z);
        const latTop = tile2lat(y, z);
        const latBot = tile2lat(y + 1, z);

        const mLeftTop = lngLatToMeters(lonLeft, latTop);
        const mRightBot = lngLatToMeters(lonRight, latBot);

        const widthMeters = Math.abs(mRightBot.x - mLeftTop.x);
        const heightMeters = Math.abs(mLeftTop.y - mRightBot.y);
        const widthWorld = widthMeters * METER_TO_WORLD;
        const heightWorld = heightMeters * METER_TO_WORLD;

        const cx =
          ((mLeftTop.x + mRightBot.x) * 0.5 - CENTER_M.x) * METER_TO_WORLD;
        const cz =
          ((mLeftTop.y + mRightBot.y) * 0.5 - CENTER_M.y) * METER_TO_WORLD;

        // 复用相同尺寸的几何体
        const gkey = `${widthWorld.toFixed(6)}x${heightWorld.toFixed(6)}`;
        let geom = this.geomCache.get(gkey);
        if (!geom) {
          geom = new THREE.PlaneGeometry(widthWorld, heightWorld, 1, 1);
          this.geomCache.set(gkey, geom);
        }

        const mat = new THREE.MeshBasicMaterial({ map: tex });
        const mesh = new THREE.Mesh(geom, mat);
        mesh.rotation.x = -Math.PI / 2;
        mesh.position.set(cx, this.yOnGround, cz);

        // 记录与入场
        this.scene.add(mesh);
        this.meshMap.set(key, { mesh, tex, stamp: performance.now() });
      })
      .catch(() => {})
      .finally(() => {
        this.loading.delete(key);
      });

    this.loading.set(key, p);
  }

  _loadImage(url) {
    return new Promise((resolve) => {
      const img = new Image();
      img.crossOrigin = "anonymous";
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
      img.src = url;
    });
  }

  /**
   * 估算合适的 zoom：
   * 让“地面米/屏幕像素”与瓦片像素密度接近。
   * 256 px/tile 的 metersPerPixel 公式：mpp = cos(lat) * 2πR / (256 * 2^z)
   * @2x（512px）等效 mpp 再 /2
   */
  _estimateZoom(widthMeters, heightMeters) {
    // 用相机看见的地面中心点的纬度估算 cos(lat)
    const mid = this.camera.position.clone();
    // 将相机投影到 y=0 得到地面点
    const t =
      -this.camera.position.y / this.camera.getWorldDirection(this.tmpVec).y;
    const groundPos = this.camera.position
      .clone()
      .add(this.camera.getWorldDirection(this.tmpVec).multiplyScalar(t));
    const my = groundPos.z / METER_TO_WORLD + CENTER_M.y;
    const lat =
      (180 / Math.PI) * (2 * Math.atan(Math.exp(my / R)) - Math.PI / 2);
    const cosLat = Math.max(0.01, Math.cos((lat * Math.PI) / 180));

    // 粗略估计屏幕像素对应的地面宽度（只要相对量级）
    const viewportPx = Math.min(
      this.renderer.domElement.width,
      this.renderer.domElement.height
    );
    const metersPerPixelTarget =
      Math.max(widthMeters, heightMeters) / viewportPx;

    // 求解 z，使 mpp(z) ≈ metersPerPixelTarget
    // mpp(z) = cosLat * 2πR / (256 * 2^z) ，@2x 则 /2
    const base = (cosLat * 2 * Math.PI * R) / 256 / 2; // /2: 因为我们用 512px 瓦片
    const z = Math.log2(base / metersPerPixelTarget);
    return Math.round(z);
  }

  /**
   * 计算屏幕四角（和中点）射线与地面 y=0 的交点，得到一个多边形
   * 用于估算当前视野在地面的覆盖范围。
   */
  _getGroundFootprint() {
    const el = this.renderer.domElement;
    const w = el.clientWidth,
      h = el.clientHeight;
    if (h === 0) return null;

    const points = [
      { x: 0, y: 0 },
      { x: w, y: 0 },
      { x: w, y: h },
      { x: 0, y: h }, // 四角
      { x: w / 2, y: 0 },
      { x: w, y: h / 2 },
      { x: w / 2, y: h },
      { x: 0, y: h / 2 }, // 辅助边中点（扩大覆盖稳定性）
    ];

    const res = [];
    for (const p of points) {
      const ndc = new THREE.Vector2((p.x / w) * 2 - 1, -(p.y / h) * 2 + 1);
      this.raycaster.setFromCamera(ndc, this.camera);
      const dir = this.raycaster.ray.direction;
      const ori = this.raycaster.ray.origin;
      const t = -ori.y / dir.y; // 与 y=0 相交参数
      if (t > 0) {
        const hit = ori.clone().add(dir.clone().multiplyScalar(t));
        res.push(hit);
      }
    }
    // 如果一个点都没打到地面（相机在地面下？）直接返回 null
    if (res.length === 0) return null;
    return res;
  }
}

const threeBox = ref(null);

let renderer, scene, camera, controls;
let animateId = null;
let lines = [];
let stations = [];
let trains = [];

// ====== WebMercator 基础：经纬度 <-> 瓦片 <-> 米坐标 ======
const R = 6378137; // WGS84 地球半径（米）
const ORIGIN_SHIFT = Math.PI * R;

const CENTER = { lng: 113.25, lat: 23.13 }; // 你的世界原点（经纬度）
const CENTER_M = lngLatToMeters(CENTER.lng, CENTER.lat);
const SCALE = 168; // 经纬度差值 → 世界坐标缩放
// 米 -> 世界坐标缩放（保持你目前视觉比例：大约 168 / 111000 ≈ 0.0015）
const METER_TO_WORLD = 0.0015; // 可微调，影响整体“城市大小”与相机远近

// ========= 地图底图（JS） =========
const basemapEnabled = ref(true);
const BASE_ZOOM = 13; // 瓦片层级（12~15 之间按清晰度/范围调整）
const TILE_SIZE = 512;
const TILE_URL = (z, x, y) =>
  `https://tile.openstreetmap.org/${z}/${x}/${y}.png`;
// 存储底图网格对象的变量
let basemapMesh = null;

// 1) 经纬度 -> 墨卡托米
function lngLatToMeters(lon, lat) {
  const x = (lon * ORIGIN_SHIFT) / 180.0;
  const y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360.0)) * R;
  return { x, y };
}

// 将经度转换为瓦片X坐标
function lon2tile(lon, zoom) {
  return Math.floor(((lon + 180) / 360) * Math.pow(2, zoom));
}
// 将纬度转换为瓦片Y坐标
function lat2tile(lat, zoom) {
  const rad = (lat * Math.PI) / 180; // 转换为弧度
  return Math.floor(
    ((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2) *
      Math.pow(2, zoom)
  );
}

function tile2lon(x, z) {
  return (x / Math.pow(2, z)) * 360 - 180;
}
function tile2lat(y, z) {
  const n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z);
  return (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}
// 根据地理数据构建底图
// async function buildBasemapFromTiles(data) {
//   // 1) 计算所有线路坐标的边界范围+
//   let minLng = Infinity,
//     minLat = Infinity,
//     maxLng = -Infinity,
//     maxLat = -Infinity;
//   data.features.forEach((f) => {
//     f.geometry.coordinates.forEach(([lng, lat]) => {
//       if (lng < minLng) minLng = lng;
//       if (lat < minLat) minLat = lat;
//       if (lng > maxLng) maxLng = lng;
//       if (lat > maxLat) maxLat = lat;
//     });
//   });
//   // 2) 计算所需的瓦片范围
//   // 2) 计算瓦片索引范围（注意：y 小在北）
//   const z = BASE_ZOOM;
//   const xMin = lon2tile(minLng, z);
//   const xMax = lon2tile(maxLng, z);
//   const yTop = lat2tile(maxLat, z); // 北端（小）
//   const yBot = lat2tile(minLat, z); // 南端（大）

//   const cols = xMax - xMin + 1;
//   const rows = yBot - yTop + 1;

//   // 3) Canvas 拼接
//   const canvas = document.createElement("canvas");
//   canvas.width = cols * TILE_SIZE; // 设置画布宽度
//   canvas.height = rows * TILE_SIZE; // 设置画布高度
//   const ctx = canvas.getContext("2d");
//   // 并行加载所有瓦片
//   const jobs = [];
//   for (let x = xMin; x <= xMax; x++) {
//     for (let y = yTop; y <= yBot; y++) {
//       // 构建瓦片URL（使用OpenStreetMap服务）
//       const url = TILE_URL(z, x, y);
//       jobs.push(
//         new Promise((resolve) => {
//           const img = new Image();
//           img.crossOrigin = "anonymous"; // 允许跨域
//           img.onload = () => {
//             // 计算当前瓦片在画布中的位置
//             const dx = (x - xMin) * TILE_SIZE;
//             const dy = (y - yTop) * TILE_SIZE;
//             // 将瓦片绘制到画布上
//             ctx.drawImage(img, dx, dy, TILE_SIZE, TILE_SIZE);
//             resolve();
//           };
//           img.onerror = () => resolve(); // 容错：失败跳过
//           img.src = url;
//         })
//       );
//     }
//   }
//   await Promise.all(jobs);

//   // 4) 由瓦片边界计算“真实的墨卡托米矩形”
//   //    左边界：xMin；右边界：xMax+1（瓦片右边缘）
//   //    上边界：yTop；下边界：yBot+1（瓦片下边缘）
//   const lonLeft = tile2lon(xMin, z);
//   const lonRight = tile2lon(xMax + 1, z);
//   const latTop = tile2lat(yTop, z);
//   const latBot = tile2lat(yBot + 1, z);

//   const mLeft = lngLatToMeters(lonLeft, latTop); // 注意：墨卡托 Y 与经纬度组合不影响 X
//   const mRight = lngLatToMeters(lonRight, latTop);
//   const mTop = lngLatToMeters(lonLeft, latTop);
//   const mBot = lngLatToMeters(lonLeft, latBot);

//   const widthMeters = Math.abs(mRight.x - mLeft.x); // 东西宽（米）
//   const heightMeters = Math.abs(mTop.y - mBot.y); // 南北高（米）

//   const widthWorld = widthMeters * METER_TO_WORLD;
//   const heightWorld = heightMeters * METER_TO_WORLD;

//   // 从Canvas创建Three.js纹理
//   const texture = new THREE.CanvasTexture(canvas);
//   texture.anisotropy = renderer.capabilities.getMaxAnisotropy?.() || 1; // 设置各向异性过滤
//   texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; // 边缘包裹模式
//   texture.minFilter = THREE.LinearMipMapLinearFilter; // 缩小过滤器
//   texture.magFilter = THREE.LinearFilter; // 放大过滤器

//   // 4) 创建底图平面几何体（尺寸与世界坐标匹配）
//   // const widthWorld = (maxLng - minLng) * SCALE; // 经度差转世界单位
//   // const heightWorld = (maxLat - minLat) * SCALE; // 纬度差转世界单位
//   const geom = new THREE.PlaneGeometry(widthWorld, heightWorld, 1, 1);
//   const mat = new THREE.MeshBasicMaterial({ map: texture }); // 使用瓦片纹理

//   const plane = new THREE.Mesh(geom, mat);
//   plane.rotation.x = -Math.PI / 2; // 旋转平面使其水平（Three.js中Y轴向上）
//   plane.matrixAutoUpdate = true;

//   // // 以范围中心对齐你的世界原点,  // 计算中心点并对齐场景原点（根据预定义的CENTER基准点）
//   // const centerX = ((minLng + maxLng) / 2 - CENTER.lng) * SCALE;
//   // const centerZ = ((minLat + maxLat) / 2 - CENTER.lat) * SCALE;
//   // plane.position.set(centerX, -0.01, centerZ); // 比轨道略低，避免 z-fighting

//   // 6) 平面中心位置（用瓦片矩形中心的“米”对齐到世界）
//   const centerLon = (lonLeft + lonRight) / 2;
//   const centerLat = (latTop + latBot) / 2;
//   const centerM = lngLatToMeters(centerLon, centerLat);

//   const px = (centerM.x - CENTER_M.x) * METER_TO_WORLD;
//   const pz = (centerM.y - CENTER_M.y) * METER_TO_WORLD;

//   plane.position.set(px, -0.01, pz); // 比轨道略低，避免 z-fighting

//   // 清理旧底图（如果存在）
//   if (basemapMesh) {
//     scene.remove(basemapMesh);
//     basemapMesh.geometry.dispose();
//     basemapMesh.material.map.dispose();
//     basemapMesh.material.dispose();
//   }
//   // 更新底图引用并添加到场景
//   basemapMesh = plane;
//   scene.add(plane);
// }

// function toggleBasemap() {
//   if (basemapEnabled.value && !basemapMesh) {
//     buildBasemapFromTiles(metroData);
//   } else if (!basemapEnabled.value && basemapMesh) {
//     scene.remove(basemapMesh);
//     basemapMesh.geometry.dispose();
//     basemapMesh.material.map.dispose();
//     basemapMesh.material.dispose();
//     basemapMesh = null;
//   }
// }

function toggleBasemap() {
  if (basemapEnabled.value) {
    tileLayer?.setEnabled(true);
    tileLayer?.update();
  } else {
    tileLayer?.setEnabled(false);
  }
}
// ========= 结束：地图底图 =========

// ========== 车辆 Hover/Click ==========
const hoveredTrain = ref(null);
const selectedTrain = ref(null);
const showInfo = ref(false);

let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();

function onMouseMove(event) {
  const rect = renderer.domElement.getBoundingClientRect();
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);

  const intersects = raycaster.intersectObjects(trains.map((t) => t.mesh));
  if (intersects.length > 0) {
    const hit = trains.find((t) => t.mesh === intersects[0].object);
    if (hoveredTrain.value && hoveredTrain.value !== hit) {
      hoveredTrain.value.mesh.material.emissive?.set("#000");
    }
    hoveredTrain.value = hit;
    hoveredTrain.value.mesh.material.emissive?.set("#00ffe0");
  } else if (hoveredTrain.value) {
    hoveredTrain.value.mesh.material.emissive?.set("#000");
    hoveredTrain.value = null;
  }
}

function onMouseClick(event) {
  const rect = renderer.domElement.getBoundingClientRect();
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(trains.map((t) => t.mesh));
  if (intersects.length > 0) {
    selectedTrain.value = trains.find((t) => t.mesh === intersects[0].object);
    showInfo.value = true;
  } else {
    showInfo.value = false;
  }
}

// 经纬度 -> Three 世界坐标（统一：WebMercator）
function lnglatToXYZ(lng, lat, y = 0) {
  const m = lngLatToMeters(lng, lat);
  return new THREE.Vector3(
    (m.x - CENTER_M.x) * METER_TO_WORLD, // 东西向
    y,
    (m.y - CENTER_M.y) * METER_TO_WORLD // 南北向（正北为 +Z）
  );
}

let tileLayer = null;

onMounted(async () => {
  // 场景
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x232323);

  // 相机
  camera = new THREE.PerspectiveCamera(
    60,
    threeBox.value.clientWidth / 700,
    0.1,
    1000
  );
  camera.position.set(12, 12, 12);
  camera.lookAt(0, 0, 0);

  // 渲染器
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(threeBox.value.clientWidth, 700);
  threeBox.value.appendChild(renderer.domElement);

  // 交互
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;

  // 灯光
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));
  const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
  dirLight.position.set(15, 25, 13);
  scene.add(dirLight);

  // 事件 & 自适应
  window.addEventListener("resize", onResize);
  threeBox.value.addEventListener("mousemove", onMouseMove, false);
  threeBox.value.addEventListener("click", onMouseClick, false);

  // 轨道与站点
  parseMetroData(metroData);

  // 底图
  // if (basemapEnabled.value) {
  //   await buildBasemapFromTiles(metroData);
  // }

  // 新：创建增量瓦片层
  tileLayer = new TileLayer(scene, renderer, camera, {
    minZoom: 12,
    maxZoom: 18,
    tilePx: 512,
    baseUrl: (z, x, y) => `https://tile.openstreetmap.org/${z}/${x}/${y}.png`,
    maxConcurrent: 10,
    maxTiles: 600,
    yOnGround: -0.01,
  });

  // 根据开关启用
  tileLayer.setEnabled(basemapEnabled.value);

  // 绑定交互更新：相机/控制器变化时刷新
  controls.addEventListener("change", () => tileLayer.update());
  window.addEventListener("resize", () => tileLayer.update());

  // 首帧更新
  tileLayer.update();

  animate();
});

onBeforeUnmount(() => {
  cancelAnimationFrame(animateId);
  window.removeEventListener("resize", onResize);
  threeBox.value?.removeEventListener("mousemove", onMouseMove);
  threeBox.value?.removeEventListener("click", onMouseClick);

  if (basemapMesh) {
    scene.remove(basemapMesh);
    basemapMesh.geometry.dispose();
    basemapMesh.material.map.dispose();
    basemapMesh.material.dispose();
    basemapMesh = null;
  }
  renderer?.dispose?.();
  scene = null;
  camera = null;
  tileLayer?.clearAll();
  tileLayer = null;
});

function onResize() {
  if (!renderer || !camera || !threeBox.value) return;
  const w = threeBox.value.clientWidth;
  const h = 700;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
}

// 解析 & 渲染
function parseMetroData(data) {
  data.features.forEach((feature) => {
    const color = feature.properties.color || "#00bbff";
    const coords = feature.geometry.coordinates;
    const points = coords.map(([lng, lat]) => lnglatToXYZ(lng, lat));
    const curve = new THREE.CatmullRomCurve3(points); //从一系列的点创建一条平滑的三维样条曲线。

    const tubeGeo = new THREE.TubeGeometry( //创建一个沿着三维曲线延伸的管道。
      curve,
      Math.max(120, points.length * 8),
      0.14,
      14,
      false
    );
    const tubeMat = new THREE.MeshStandardMaterial({
      //标准网格材质
      color,
      metalness: 0.38,
      roughness: 0.42,
    });
    const tubeMesh = new THREE.Mesh(tubeGeo, tubeMat);
    scene.add(tubeMesh);
    lines.push(tubeMesh);

    if (feature.properties.stations) {
      feature.properties.stations.forEach((station, idx) => {
        if (points[idx]) {
          const sphere = new THREE.Mesh(
            new THREE.SphereGeometry(0.24, 24, 24), //球缓冲几何体
            new THREE.MeshStandardMaterial({
              color: "#ffd600",
              emissive: "#fff46c",
              emissiveIntensity: 0.3,
            })
          );
          sphere.position.copy(points[idx]);
          sphere.userData = {
            name: station.name,
            order: station.order,
            line: feature.properties.line_name,
          };
          scene.add(sphere);
          stations.push(sphere);
        }
      });
    }

    for (let i = 0; i < 2; i++) {
      const train = new THREE.Mesh(
        new THREE.BoxGeometry(0.6, 0.3, 0.28),
        new THREE.MeshStandardMaterial({ color: "#ea3366", metalness: 0.5 })
      );
      train.userData = {
        lineName: feature.properties.line_name,
        color: feature.properties.color,
        trainId: `${feature.properties.line_name}-${i + 1}`,
      };
      scene.add(train);
      trains.push({
        mesh: train,
        curve,
        t: i * 0.4,
        speed: Math.random() * 0.0005, // 直观地动起来
        feature,
      });
    }
  });
}

// 动画
function animate() {
  // 请求下一帧动画，形成动画循环
  animateId = requestAnimationFrame(animate);
  // 遍历所有火车对象
  trains.forEach((obj) => {
    // 更新火车在曲线上的位置参数（基于速度）
    obj.t += obj.speed;
    // 位置参数超过1时重置（相当于循环路径）
    if (obj.t > 1) obj.t = 0;
    // 获取当前t值对应的曲线位置坐标
    const pos = obj.curve.getPointAt(obj.t);
    // 获取当前点的切线方向（用于火车朝向）
    const tangent = obj.curve.getTangentAt(obj.t);
    // 更新火车模型位置
    obj.mesh.position.copy(pos);
    // 设置火车朝向：将默认X轴旋转到切线方向
    obj.mesh.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), tangent);
  });
  controls.update();
  tileLayer?.update(); // 轻量操作，含节流
  renderer.render(scene, camera);
}

// 业务：计算最近站点
function getNearestStation(trainObj) {
  // 获取火车当前所在曲线
  const curve = trainObj.curve;
  // 获取火车当前位置
  const pos = curve.getPointAt(trainObj.t);
  console.log(pos);

  // 将地理坐标转换为三维坐标
  const coords = trainObj.feature.geometry.coordinates.map(([lng, lat]) =>
    lnglatToXYZ(lng, lat)
  );
  console.log(coords);

  // 初始化最小距离和最近站点索引
  let minDist = Infinity,
    nearestIdx = -1;
  // 遍历所有站点坐标
  coords.forEach((pt, idx) => {
    const dist = pos.distanceTo(pt);
    if (dist < minDist) {
      minDist = dist;
      nearestIdx = idx;
    }
  });
  const s = trainObj.feature.properties.stations;
  return s && s[nearestIdx] ? s[nearestIdx].name : "未知";
}
</script>
